package tp.badugi.net;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.stereotype.Component;

import play.mvc.WebSocket;

import com.fasterxml.jackson.databind.JsonNode;

import tp.badugi.net.exceptions.IllegalCharactersException;
import tp.badugi.net.exceptions.IllegalParameterValueException;
import tp.badugi.net.exceptions.NameTakenException;
import tp.badugi.net.exceptions.NoSuchTableException;
import tp.badugi.net.exceptions.ParameterOutOfBoundsException;
import tp.badugi.net.exceptions.RequestTimedOutException;
import tp.badugi.net.exceptions.ServerCouldNotCompleteRequestException;
import tp.badugi.net.exceptions.TableFullException;
import static tp.badugi.net.ClientNetworkingChannel.*;
import play.libs.F.*;

/**
 * This class provides a server side network communication API with the client.
 * 
 * @author bartek
 *
 */
@Component
public class ServerNetworkingChannel {
	
	static final boolean DEBUGGING_ON = false;
	private static final int PING_FREQUENCY = 10000;
	private static final int TIMEOUT_GRACE_PERIOD = 5000;
	
	public static final String DELIMITER = ";";
	public static final String REQUEST = "REQUEST";
	public static final String PARAM_RETURN_RESULT = "PARAM_RETURN_RESULT";
	public static final String PARAM_EXCEPTION = "PARAM_EXCEPTION";
	public static final String PARAM_EXCEPTION_MESSAGE = "PARAM_EXCEPTION_MESSAGE";
	public static final String PARAM_POSSIBLE_ACTIONS = "PARAM_POSSIBLE_ACTIONS";
	public static final String PARAM_PLAYER_INDEX = "PARAM_PLAYER_NUMBER";
	public static final String PARAM_PLAYER_NAME = "PARAM_PLAYER_NAME";
	public static final String PARAM_PLAYER_NAMES = "PARAM_PLAYER_NAMES";
	public static final String PARAM_GAME_STATUS = "PARAM_GAME_STATUS";
	public static final String PARAM_BET_AMOUNT = "PARAM_BET_AMOUNT";
	public static final String PARAM_MIN_BET = "PARAM_MIN_BET";
	public static final String PARAM_TIMEOUT = "PARAM_TIMEOUT";
	public static final String PARAM_CURRENT_CARDS = "PARAM_CURRENT_CARDS";
	public static final String PARAM_TABLE_LIST = "PARAM_TABLE_LIST";
	public static final String PARAM_ACTION_STRING = "PARAM_ACTION_STRING";
	public static final String PARAM_CARDS_STRING = "PARAM_CARDS_STRING";
	public static final String PARAM_ACTION = "PARAM_ACTION";
	public static final String PARAM_AMOUNT = "PARAM_AMOUNT";
	public static final String PARAM_CARD_COUNT = "PARAM_CARD_COUNT";
	public static final String PARAM_BETTING_ROUND = "PARAM_BETTING_ROUND";
	public static final String PARAM_REVEALED_CARDS = "PARAM_REVEALED_CARDS";
	public static final String PARAM_WINNER_INDEXES = "PARAM_WINNER_INDEXES";
	public static final String PARAM_UPDATED_CHIP_COUNTS = "PARAM_UPDATED_CHIP_COUNTS";
	public static final String PARAM_CARDS = "PARAM_CARDS";
	public static final String PARAM_TIME_UNTIL_START = "PARAM_TIME_UNTIL_START";
	public static final String PARAM_TIME_UNTIL_RESTART = "PARAM_TIME_UNTIL_RESTART";
	public static final String PARAM_MSG = "PARAM_MSG";
	public static final String ACTION_FOLD = "ACTION_FOLD";
	public static final String ACTION_CHECK = "ACTION_CHECK";
	public static final String ACTION_BET = "ACTION_BET";
	public static final String ACTION_CALL = "ACTION_CALL";
	public static final String ACTION_RAISE = "ACTION_RAISE";
	public static final String ACTION_ALLIN = "ACTION_ALLIN";
	public static final String REQUEST_ACTION = "REQUEST_ACTION";
	public static final String REQUEST_CARDS = "REQUEST_CARDS";
	public static final String RESPONSE_CREATE_TABLE = "RESPONSE_CREATE_TABLE";
	public static final String RESPONSE_JOIN_TABLE = "RESPONSE_JOIN_TABLE";
	public static final String RESPONSE_LIST_TABLES = "RESPONSE_LIST_TABLES";
	public static final String RESPONSE_LEAVE_TABLE = "RESPONSE_LEAVE_TABLE";
	public static final String NOTIFY_CARDS_RECEIVED = "NOTIFY_CARDS_RECEIVED";
	public static final String NOTIFY_BETTING_ROUND_CHANGED = "NOTIFY_BETTING_ROUND_CHANGED";
	public static final String NOTIFY_PLAYER_DISCONNECTED = "NOTIFY_PLAYER_DISCONNECTED";
	public static final String NOTIFY_PLAYER_IN_ACTION_CHANGED = "NOTIFY_PLAYER_IN_ACTION_CHANGED";
	public static final String NOTIFY_PLAYER_JOINED = "NOTIFY_PLAYER_JOINED";
	public static final String NOTIFY_PLAYER_LEFT = "NOTIFY_PLAYER_LEFT";
	public static final String NOTIFY_PLAYER_PERFORMED_ACTION = "NOTIFY_PLAYER_PERFORMED_ACTION";
	public static final String NOTIFY_PLAYER_RECONNECTED = "NOTIFY_PLAYER_RECONNECTED";
	public static final String NOTIFY_PLAYER_REPLACED_CARDS = "NOTIFY_PLAYER_REPLACED_CARDS";
	public static final String NOTIFY_PLAYER_ON_BUTTON_CHANGED = "NOTIFY_PLAYER_ON_BUTTON_CHANGED";
	public static final String NOTIFY_WINNER_REVEALED = "NOTIFY_WINNER_REVEALED";
	public static final String NOTIFY_GAME_ABOUT_TO_START = "NOTIFY_GAME_ABOUT_TO_START";
	public static final String NOTIFY_GAME_ABOUT_TO_RESTART = "NOTIFY_GAME_ABOUT_TO_RESTART";
	public static final String PING_PROBE = "PING_PROBE";
	public static final String ERROR_MSG_TABLE_NAME_ILLEGAL_CHARACTERS = 
			"Table name contains illegal characters.";
	public static final String ERROR_MSG_PLAYER_NAME_ILLEGAL_CHARACTERS = 
			"Player name contains illegal characters.";
	public static final String ERROR_MSG_PREPARING_REQUEST = "Error preparing request.";
	public static final String ERROR_MSG_PREPARING_RESPONSE = "Error preparing response.";		
	
	private iTableRequestListener tableListener;
	private WebSocket.In<String> in;
	private WebSocket.Out<String> out;
	private ActionRequestResponse actionRequestResponse;
	private String[] cardsRequestResponse;

	public ServerNetworkingChannel(){}
		
	public ServerNetworkingChannel(WebSocket.In<String> in, WebSocket.Out<String> out) {
		this.in = in;
		this.out = out;
		
		in.onMessage(new Callback<String>()
        {
            @Override
            public void invoke(String message)
            {
            	try {
	            	if (DEBUGGING_ON) {
						String time = getTime();
						System.out.println(time + ": Server received message: " + message);
					}
	            	JSONObject jsonMessage = new JSONObject(message);
					String request = jsonMessage.getString(REQUEST);
					switch (request) {
					case RESPONSE_CHOSEN_ACTION:
						handleActionChosenResponse(jsonMessage);
						break;
					case RESPONSE_CHOSEN_CARDS:
						handleCardsChosenResponse(jsonMessage);
						break;
					default:
						throw new IllegalArgumentException(
								"Don't know how to handle request: " + request);
					}
            	}
				catch (JSONException | IllegalArgumentException e) {
					System.err.println("Packet not properly formatted on client side.");
					e.printStackTrace(); // TODO Remove when finished testing
				}
            }
        });

        in.onClose(new Callback0()
        {
            @Override
            public void invoke()
            {
            	handleDisconnect();
            }
        });
	}
	
	private void handleDisconnect() {
		if (tableListener != null) {
			tableListener.leaveTable();
		}
	}

	public synchronized ActionRequestResponse requestAction(
			String[] possibleActions, 
			long betAmount, 
			long minBet, 
			int timeout
	) throws RequestTimedOutException {	
		// Try to prepare JSON formatted request
		try {
			JSONObject jsonEncodedParameters = new JSONObject();
			jsonEncodedParameters.put(PARAM_POSSIBLE_ACTIONS, possibleActions);
			jsonEncodedParameters.put(PARAM_BET_AMOUNT, betAmount);
			jsonEncodedParameters.put(PARAM_MIN_BET, minBet);
			jsonEncodedParameters.put(PARAM_TIMEOUT, timeout);
			jsonEncodedParameters.put(PARAM_MSG, "[Request] What is your action?");
			jsonEncodedParameters.put(REQUEST, REQUEST_ACTION);
			
			String requestMessage = jsonEncodedParameters.toString();
			
			// Synchronize threads in order to exchange request and response
			synchronized (this) {
				// Flush previous response and send request
				actionRequestResponse = null;
				sendMessage(requestMessage);
	
				// Wait for response 
				long timeToWait = timeout + TIMEOUT_GRACE_PERIOD;
				long timeAtStart = System.currentTimeMillis();
				while (actionRequestResponse == null) {
					try {
						wait(timeToWait);
						timeToWait -= System.currentTimeMillis() - timeAtStart;
						if (timeToWait <= 0) {
							throw new RequestTimedOutException("The client did not respond.");
						}
					}
					catch (InterruptedException e) {
						String message = "Interrupted while waiting for response from client.";
						throw new RequestTimedOutException(message);
					}
				}
				
				// Return result
				return actionRequestResponse;
			}
		}
		
		// Try to recover from request formatting error.
		catch (JSONException e) {
			System.err.println(ERROR_MSG_PREPARING_REQUEST);
			e.printStackTrace();
			return new ActionRequestResponse(ACTION_FOLD, 0);
		}
	}
	
	public synchronized String[] requestCards(String[] currentCards, int timeout) 
			throws RequestTimedOutException 
	{
		// Try to prepare JSON formatted request
		try {
			JSONObject jsonEncodedParameters = new JSONObject();
			jsonEncodedParameters.put(PARAM_CURRENT_CARDS, currentCards);
			jsonEncodedParameters.put(PARAM_TIMEOUT, timeout);
			jsonEncodedParameters.put(PARAM_MSG, "[Request] Which cards would you like exchanged?");
			jsonEncodedParameters.put(REQUEST, REQUEST_CARDS);
			
			String requestMessage = jsonEncodedParameters.toString();
			
			// Synchronize threads in order to exchange request and response
			synchronized (this) {
				// Flush previous response and send request
				cardsRequestResponse = null;
				sendMessage(requestMessage);
				
				// Wait for response 
				long timeToWait = timeout + TIMEOUT_GRACE_PERIOD;
				long timeAtStart = System.currentTimeMillis();
				while (cardsRequestResponse == null) {
					try {
						wait(timeToWait);
						timeToWait -= System.currentTimeMillis() - timeAtStart;
						if (timeToWait <= 0) {
							throw new RequestTimedOutException("The client did not respond.");
						}
					}
					catch (InterruptedException e) {
						String message = "Interrupted while waiting for response from client.";
						throw new RequestTimedOutException(message);
					}
				}
				
				if (cardsRequestResponse.length == 0) {
					cardsRequestResponse = null;
				}
				
				// Return result
				return cardsRequestResponse;
			}
		}
		// Try to recover from request formatting error.
		catch (JSONException e) {
			System.err.println(ERROR_MSG_PREPARING_REQUEST);
			e.printStackTrace();
			return null;
		}
	}
	
	public synchronized void notifyCardsReceived(String[] cards, GameStatus gameStatus) {
		// Try to prepare JSON formatted request
		try {
			JSONObject parameters = new JSONObject();
			parameters.put(PARAM_CARDS, cards);
			parameters.put(PARAM_GAME_STATUS, gameStatus.toJSONObject());
			parameters.put(PARAM_MSG, "[Info] Your cards: " 
					+ cards[0] + " " + cards[1] + " " + cards[2] + " " + cards[3] + ".");
			parameters.put(REQUEST, NOTIFY_CARDS_RECEIVED);
			
			String notificationMessage = parameters.toString();
			sendMessage(notificationMessage);
		}
		// Try to recover from request formatting error.
		catch (JSONException e) {
			System.err.println(ERROR_MSG_PREPARING_REQUEST);
			e.printStackTrace();
		}
	}
	
	public synchronized void notifyPlayerJoined(int playerIndex, GameStatus gameStatus) 
	{
		// Try to prepare JSON formatted request
		try {
			JSONObject parameters = new JSONObject();
			parameters.put(PARAM_PLAYER_INDEX, playerIndex);
			parameters.put(PARAM_GAME_STATUS, gameStatus.toJSONObject());
			parameters.put(PARAM_MSG, "[Info] " + gameStatus.playerNames[playerIndex] + " joined the game.");
			parameters.put(REQUEST, NOTIFY_PLAYER_JOINED);

			String notificationMessage = parameters.toString();
			sendMessage(notificationMessage);
		}
		// Try to recover from request formatting error.
		catch (JSONException e) {
			System.err.println(ERROR_MSG_PREPARING_REQUEST);
			e.printStackTrace();
		}
	}
	
	public synchronized void notifyPlayerLeft(String playerName, GameStatus gameStatus) 
	{
		// Try to prepare JSON formatted request
		try {
			JSONObject parameters = new JSONObject();
			parameters.put(PARAM_PLAYER_NAME, playerName);
			parameters.put(PARAM_GAME_STATUS, gameStatus.toJSONObject());
			parameters.put(PARAM_MSG, "[Info] " + playerName + " left the game.");
			parameters.put(REQUEST, NOTIFY_PLAYER_LEFT);

			String notificationMessage = parameters.toString();
			sendMessage(notificationMessage);
		}
		// Try to recover from request formatting error.
		catch (JSONException e) {
			System.err.println(ERROR_MSG_PREPARING_REQUEST);
			e.printStackTrace();
		}
	}
	
	public synchronized void notifyPlayerDisconnected(String playerName, GameStatus gameStatus) 
	{
		// Try to prepare JSON formatted request
		try {
			JSONObject parameters = new JSONObject();
			parameters.put(PARAM_PLAYER_NAME, playerName);
			parameters.put(PARAM_GAME_STATUS, gameStatus.toJSONObject());
			parameters.put(PARAM_MSG, "[Info] " + playerName + " disconnected.");
			parameters.put(REQUEST, NOTIFY_PLAYER_DISCONNECTED);
			
			String notificationMessage = parameters.toString();
			sendMessage(notificationMessage);
		}
		// Try to recover from request formatting error.
		catch (JSONException e) {
			System.err.println(ERROR_MSG_PREPARING_REQUEST);
			e.printStackTrace();
		}
	}
	
	public synchronized void notifyPlayerReconnected(String playerName, GameStatus gameStatus) 
	{
		// Try to prepare JSON formatted request
		try {
			JSONObject parameters = new JSONObject();
			parameters.put(PARAM_PLAYER_NAME, playerName);
			parameters.put(PARAM_GAME_STATUS, gameStatus.toJSONObject());
			parameters.put(PARAM_MSG, "[Info] " + playerName + " reconected.");
			parameters.put(REQUEST, NOTIFY_PLAYER_RECONNECTED);

			String notificationMessage = parameters.toString();
			sendMessage(notificationMessage);
		}
		// Try to recover from request formatting error.
		catch (JSONException e) {
			System.err.println(ERROR_MSG_PREPARING_REQUEST);
			e.printStackTrace();
		}
	}
	
	public synchronized void notifyPlayerInActionChanged(
			int playerIndex, 
			String[] playerNames, 
			GameStatus gameStatus) 
	{
		// Try to prepare JSON formatted request
		try {
			JSONObject parameters = new JSONObject();
			parameters.put(PARAM_PLAYER_INDEX, playerIndex);
			parameters.put(PARAM_PLAYER_NAMES, playerNames);
			parameters.put(PARAM_GAME_STATUS, gameStatus.toJSONObject());
			parameters.put(PARAM_MSG, "[Info] Waiting for " + playerNames[playerIndex] + ".");
			parameters.put(REQUEST, NOTIFY_PLAYER_IN_ACTION_CHANGED);

			String notificationMessage = parameters.toString();
			sendMessage(notificationMessage);
		}
		// Try to recover from request formatting error.
		catch (JSONException e) {
			System.err.println(ERROR_MSG_PREPARING_REQUEST);
			e.printStackTrace();
		}
	}
	
	public synchronized void notifyPlayerWithButtonChanged(
			int playerIndex, 
			String[] playerNames,
			GameStatus gameStatus) 
	{
		// Try to prepare JSON formatted request
		try {
			JSONObject parameters = new JSONObject();
			parameters.put(PARAM_PLAYER_INDEX, playerIndex);
			parameters.put(PARAM_PLAYER_NAMES, playerNames);
			parameters.put(PARAM_GAME_STATUS, gameStatus.toJSONObject());
			parameters.put(REQUEST, NOTIFY_PLAYER_ON_BUTTON_CHANGED);

			String notificationMessage = parameters.toString();
			sendMessage(notificationMessage);
		}
		// Try to recover from request formatting error.
		catch (JSONException e) {
			System.err.println(ERROR_MSG_PREPARING_REQUEST);
			e.printStackTrace();
		}
	}
	
	public synchronized void notifyPlayerPerformedAction(
			int playerIndex,
			String action, 
			long amount,
			GameStatus gameStatus) 
	{
		// Try to prepare JSON formatted request
		try {
			JSONObject parameters = new JSONObject();
			String msg = "[Info] " + gameStatus.playerNames[playerIndex];
			switch (action) {
			case ACTION_FOLD:
				msg += " folded.";
				break;
			case ACTION_CHECK:
				msg += " checked.";
				break;
			case ACTION_CALL:
				msg += " called.";
				break;
			case ACTION_BET:
				msg += " bet " + amount + ".";
				break;
			case ACTION_RAISE:
				msg += " raised " + amount + ".";
				break;
			case ACTION_ALLIN:
				msg += " went all-in.";
				break;
			}
			parameters.put(PARAM_PLAYER_INDEX, playerIndex);
			parameters.put(PARAM_ACTION, action);
			parameters.put(PARAM_AMOUNT, amount);
			parameters.put(PARAM_GAME_STATUS, gameStatus.toJSONObject());
			parameters.put(PARAM_MSG, msg);
			parameters.put(REQUEST, NOTIFY_PLAYER_PERFORMED_ACTION);
			
			String notificationMessage = parameters.toString();
			sendMessage(notificationMessage);
		}
		// Try to recover from request formatting error.
		catch (JSONException e) {
			System.err.println(ERROR_MSG_PREPARING_REQUEST);
			e.printStackTrace();
		}
	}
	
	public synchronized void notifyPlayerReplacedCards(
			String playerName, 
			int cardCount,
			GameStatus gameStatus) 
	{
		// Try to prepare JSON formatted request
		try {
			JSONObject parameters = new JSONObject();
			parameters.put(PARAM_PLAYER_NAME, playerName);
			parameters.put(PARAM_CARD_COUNT, cardCount);
			parameters.put(PARAM_GAME_STATUS, gameStatus.toJSONObject());
			parameters.put(PARAM_MSG, "[Info] " + playerName + " replaced " + cardCount + " cards.");
			parameters.put(REQUEST, NOTIFY_PLAYER_REPLACED_CARDS);
			
			String notificationMessage = parameters.toString();
			sendMessage(notificationMessage);
		}
		// Try to recover from request formatting error.
		catch (JSONException e) {
			System.err.println(ERROR_MSG_PREPARING_REQUEST);
			e.printStackTrace();
		}
	}
	
	public synchronized void notifyBettingRoundChanged(int bettingRound, GameStatus gameStatus) {
		// Try to prepare JSON formatted request
		try {
			JSONObject parameters = new JSONObject();
			parameters.put(PARAM_BETTING_ROUND, bettingRound);
			parameters.put(REQUEST, NOTIFY_BETTING_ROUND_CHANGED);
			parameters.put(PARAM_GAME_STATUS, gameStatus.toJSONObject());
			
			String notificationMessage = parameters.toString();
			sendMessage(notificationMessage);
		}
		// Try to recover from request formatting error.
		catch (JSONException e) {
			System.err.println(ERROR_MSG_PREPARING_REQUEST);
			e.printStackTrace();
		}
	}
	
	public synchronized void notifyWinnerRevealed(
			String[][] revealedCards,
			int[] winnerIndexes, 
			String[] playerNames, 
			long[] updatedChipCounts) 
	{
		// Try to prepare JSON formatted request
		try {
			String msg = "[Info] ";
			for (int i = 0; i < winnerIndexes.length; i++) {
				int winnerID = winnerIndexes[i];
				msg += i == 0 ? playerNames[winnerID] : " and " + playerNames[winnerID];
			}
			msg += " won!";
			for (int i = 0; i < winnerIndexes.length; i++) {
				int winnerID = winnerIndexes[i];
				if (revealedCards[winnerID][0] != null) {
					msg += " " + playerNames[winnerID] + "'s cards:";
					for (int j = 0; j < 4; j++) {
						msg += " " + revealedCards[winnerID][j];
					}
				}
			}
			
			JSONObject parameters = new JSONObject();
			parameters.put(PARAM_REVEALED_CARDS, revealedCards);
			parameters.put(PARAM_WINNER_INDEXES, winnerIndexes);
			parameters.put(PARAM_PLAYER_NAMES, playerNames);
			parameters.put(PARAM_UPDATED_CHIP_COUNTS, updatedChipCounts);
			parameters.put(PARAM_MSG, msg);
			parameters.put(REQUEST, NOTIFY_WINNER_REVEALED);
			
			String notificationMessage = parameters.toString();
			sendMessage(notificationMessage);
		}
		// Try to recover from request formatting error.
		catch (JSONException e) {
			System.err.println(ERROR_MSG_PREPARING_REQUEST);
			e.printStackTrace();
		}
	}

	private void handleActionChosenResponse(JSONObject jsonMessage) throws JSONException {
		// Decode JSON encoded response
		String action = null;
		long amount = 0;
		if (!jsonMessage.isNull(PARAM_ACTION_STRING)) {
			String[] tokens = jsonMessage.getString(PARAM_ACTION_STRING).split(" ");
			if (tokens.length >= 1) {
				switch(tokens[0].toUpperCase()) {
				case "FOLD":
					action = ACTION_FOLD;
					break;
				case "CHECK":
					action = ACTION_CHECK;
					break;
				case "BET":
					action = ACTION_BET;
					break;
				case "CALL":
					action = ACTION_CALL;
					break;
				case "RAISE":
					action = ACTION_RAISE;
					break;
				case "ALLIN":
					action = ACTION_ALLIN;
					break;
				}
			}
			if (tokens.length >= 2) {
				try {
					amount = Long.parseLong(tokens[1]);
				}
				catch (NumberFormatException e) {
					amount = 0;
				}
			}
		}
		ActionRequestResponse response = new ActionRequestResponse(action, amount);
		
		// Synchronize threads in order to exchange request and response
		synchronized (this) {
			// Set response
			actionRequestResponse = response;
			notifyAll();
		}
	}
	
	private void handleCardsChosenResponse(JSONObject jsonMessage) throws JSONException {
		// Decode JSON encoded response
		String[] chosenCards = null;
		if (!jsonMessage.isNull(PARAM_CARDS_STRING)) {
			chosenCards = jsonMessage.getString(PARAM_CARDS_STRING).split(" ");
			for (int i = 0; i < chosenCards.length; i++) {
				chosenCards[i] = chosenCards[i].toUpperCase();
			}
		}
		
		// Synchronize threads in order to exchange request and response
		synchronized (this) {
			// Set response
			cardsRequestResponse = chosenCards;
			notifyAll();
		}
	}
	
	private synchronized void sendMessage(String formattedResponse) {
		out.write(formattedResponse);
		if (DEBUGGING_ON) {
			String time = getTime();
			System.out.println(time + ": Server sent message: " + formattedResponse);
		}
	}
	
	private String getTime() {
		DateFormat dateFormat = new SimpleDateFormat("HH:mm:ss");
		Calendar cal = Calendar.getInstance();
		String time = dateFormat.format(cal.getTime());
		return time;
	}

	public void notifyGameAboutToStart(int timeUntilStart, GameStatus gameStatus) {
		// Try to prepare JSON formatted request
		try {
			JSONObject parameters = new JSONObject();
			parameters.put(PARAM_TIME_UNTIL_START, timeUntilStart);
			parameters.put(PARAM_GAME_STATUS, gameStatus.toJSONObject());
			parameters.put(PARAM_MSG, "[Info] The game will start in " + timeUntilStart + " sec.");
			parameters.put(REQUEST, NOTIFY_GAME_ABOUT_TO_START);

			String notificationMessage = parameters.toString();
			sendMessage(notificationMessage);
		}
		// Try to recover from request formatting error.
		catch (JSONException e) {
			System.err.println(ERROR_MSG_PREPARING_REQUEST);
			e.printStackTrace();
		}
	}
	
	public void notifyGameAboutToRestart(int timeUntilRestart) {
		// Try to prepare JSON formatted request
		try {
			JSONObject parameters = new JSONObject();
			parameters.put(PARAM_TIME_UNTIL_RESTART, timeUntilRestart);
			parameters.put(REQUEST, NOTIFY_GAME_ABOUT_TO_RESTART);

			String notificationMessage = parameters.toString();
			sendMessage(notificationMessage);
		}
		// Try to recover from request formatting error.
		catch (JSONException e) {
			System.err.println(ERROR_MSG_PREPARING_REQUEST);
			e.printStackTrace();
		}
	}

	public void setTableListener(iTableRequestListener tableListener) {
		this.tableListener = tableListener;
	}
}
